#include <strmif.h>
#include "mkvparserstreamaudio.hpp"
#include "mkvparser.hpp"
#include <cassert>
//#include <algorithm>
#include <limits>
//#include <climits>
//#include <vfwmsgs.h>
//#include <sstream>
//#include <iomanip>
//#include <dvdmedia.h>
#include <uuids.h>
#include <mmreg.h>
#ifdef _DEBUG
#include "odbgstream.hpp"
using std::endl;
#endif


namespace MkvParser
{


AudioStream* AudioStream::CreateInstance(AudioTrack* pTrack)
{
    assert(pTrack);
    
    const char* const id = pTrack->GetCodecId();
    assert(id);  //TODO
    
    if (_stricmp(id, "A_AC3") == 0)
        __noop;
    else
        return 0;  //can't create a stream from this track
        
    //TODO: vet settings, etc    

    AudioStream* const s = new (std::nothrow) AudioStream(pTrack);
    assert(s);  //TODO
    
    return s;
}


AudioStream::AudioStream(AudioTrack* pTrack) : Stream(pTrack)
{
}


std::wostream& AudioStream::GetKind(std::wostream& os) const
{
    return os << L"Audio";
}


void AudioStream::GetMediaTypes(CMediaTypes& mtv) const
{
    mtv.Clear();
    
    const char* const id = m_pTrack->GetCodecId();
    assert(id);
    
    if (_stricmp(id, "A_AC3") == 0)
        GetAc3MediaTypes(mtv);
}


void AudioStream::GetAc3MediaTypes(CMediaTypes& mtv) const
{
    AM_MEDIA_TYPE mt;
    WAVEFORMATEX wfx;
    
    mt.majortype = MEDIATYPE_Audio;
    mt.subtype = MEDIASUBTYPE_DOLBY_AC3;
    mt.bFixedSizeSamples = FALSE;
    mt.bTemporalCompression = FALSE;
    mt.lSampleSize = 0;
    mt.formattype = FORMAT_WaveFormatEx;
    mt.pUnk = 0;
    mt.cbFormat = sizeof wfx;
    mt.pbFormat = (BYTE*)&wfx;
    
    const AudioTrack* const pTrack = static_cast<AudioTrack*>(m_pTrack);
    
    const __int64 channels = pTrack->GetChannels();
    assert(channels > 0);
    assert(channels <= std::numeric_limits<WORD>::max());
    //TODO: does AC-3 have a limit to channel count?

    const float rate = pTrack->GetSamplingRate();
    assert(rate > 0);
    
    float intrate_;
    const float fracrate = modf(rate, &intrate_);
    fracrate;
    assert(fracrate == 0);
    
    const int intrate = static_cast<int>(intrate_);
    assert((intrate == 48000) || (intrate == 44100) || (intrate == 32000));
    
    wfx.wFormatTag = WAVE_FORMAT_UNKNOWN;  //TODO
    wfx.nChannels = static_cast<WORD>(channels);
    wfx.nSamplesPerSec = static_cast<DWORD>(intrate);
    wfx.nAvgBytesPerSec = 0;  //TODO: use s_frmsizcod_table for this
    wfx.nBlockAlign = 0;      //TODO
    wfx.wBitsPerSample = 0;   //TODO
    wfx.cbSize = 0;
    
    mtv.Add(mt);    
}


HRESULT AudioStream::QueryAccept(const AM_MEDIA_TYPE* pmt) const
{
    if (pmt == 0)
        return E_INVALIDARG;
        
    const AM_MEDIA_TYPE& mt = *pmt;
    
    if (mt.majortype != MEDIATYPE_Audio)
        return S_FALSE;
        
    const char* const id = m_pTrack->GetCodecId();
    assert(id);
    
    if (_stricmp(id, "A_AC3") == 0)
    {
        if (mt.subtype != MEDIASUBTYPE_DOLBY_AC3)
            return S_FALSE;
            
        return S_OK;  //TODO: more vetting here
    }

    return S_FALSE;  
}


HRESULT AudioStream::UpdateAllocatorProperties(
    ALLOCATOR_PROPERTIES& props) const
{
    if (props.cBuffers <= 0)
        props.cBuffers = 1;
        
    const long size = GetBufferSize();
    
    if (props.cbBuffer < size)
        props.cbBuffer = size;
        
    if (props.cbAlign <= 0)
        props.cbAlign = 1;
        
    if (props.cbPrefix < 0)
        props.cbPrefix = 0;
    
    return S_OK;
}


long AudioStream::GetBufferSize() const
{
    const AudioTrack* const pTrack = static_cast<AudioTrack*>(m_pTrack);
    
    const float rr = pTrack->GetSamplingRate();
    const long nSamplesPerSec = static_cast<long>(rr);
    
    const __int64 cc = pTrack->GetChannels();
    const long nChannels = static_cast<long>(cc);
    
    const long nBlockAlign = nChannels * 2;  //16-bit PCM
    const long nAvgBytesPerSec = nBlockAlign * nSamplesPerSec;
        
    const long size = nAvgBytesPerSec;  //TODO: make a better estimate
    
    return size;
}


#if 0  //TODO: use this for incremental loading
HRESULT AudioStream::PopulateSample(
    IMediaSample* pSample, 
    __int64& result)
{
    if (pSample == 0)
        return E_INVALIDARG;

    if (m_pCurr == m_pStop)  //end-of-stream
    {
#ifdef _DEBUG
        odbgstream os;

        os << "AudioStream::PopulateSample: EOS detected";
        
        if (m_pCurr)
            os << "; time=" << m_pCurr->GetTime();
            
        os << endl;
#endif

        result = 0;        
        return S_FALSE;
    }
    
    assert(m_baseTime >= 0);
    assert((m_baseTime % 100) == 0);
    assert(m_pCurr);
    assert(m_pCurr->GetNumber() == m_pTrack->GetNumber());    
    assert(m_pCurr->GetTime() >= m_baseTime);
    assert((m_pStop == 0) || 
           (m_pStop->GetTimeCode() > m_pCurr->GetTimeCode()));
    
    const Block* const pNextBlock = m_pTrack->GetNext(m_pCurr, result);

    if (result < 0)
        return static_cast<HRESULT>(result);
        
    if (result > 0)
        return S_FALSE;  //can't make progress until we have next frame too
        
    const LONG srcsize = m_pCurr->GetSize();
    assert(srcsize >= 0);
    
    const long tgtsize = pSample->GetSize();
    tgtsize;
    assert(tgtsize >= 0);
    assert(tgtsize >= srcsize);
    
    BYTE* ptr;

    HRESULT hr = pSample->GetPointer(&ptr);  //read srcsize bytes
    assert(SUCCEEDED(hr));
    assert(ptr);
    
    hr = m_pCurr->Read(ptr);
    assert(hr == S_OK);  //all bytes were read
    
    hr = pSample->SetActualDataLength(srcsize);
    
    hr = pSample->SetPreroll(0);
    assert(SUCCEEDED(hr));
    
    hr = pSample->SetMediaType(0);
    assert(SUCCEEDED(hr));
    
    hr = pSample->SetDiscontinuity(m_bDiscontinuity);
    assert(SUCCEEDED(hr));
    m_bDiscontinuity = false;

    //TODO: this is meaningful for audio streams.  MediaTime is the accumulated
    //sum of audio samples.
    hr = pSample->SetMediaTime(0, 0);
    assert(SUCCEEDED(hr));
    
    const BlockGroup* const pGroup = m_pCurr->m_pGroup;
    assert(pGroup);
    
    //TODO: you could make an argument that every audio frame is a key frame.
    //Here we decide by fiat that discussion of sync point is only meaningful
    //for video streams, so for audio sync point status is always false.
    hr = pSample->SetSyncPoint(FALSE);
    assert(SUCCEEDED(hr));
    
    const __int64 start_ns = m_pCurr->GetTime();
    assert(start_ns >= m_baseTime);
    assert((start_ns % 100) == 0);
    
    __int64 ns = start_ns - m_baseTime;    
    __int64 start_reftime = ns / 100;
    
    __int64 stop_reftime;
    __int64* pstop_reftime;
            
    if (pNextBlock == 0)
        pstop_reftime = 0;  //TODO: use duration of curr block
    else
    {
        const __int64 stop_ns = pNextBlock->GetTime();
        assert(stop_ns > start_ns);
        assert((stop_ns % 100) == 0);

        ns = stop_ns - m_baseTime;
        stop_reftime = ns / 100;
        assert(stop_reftime > start_reftime);

        pstop_reftime = &stop_reftime;
    }
    
    hr = pSample->SetTime(&start_reftime, pstop_reftime);
    assert(SUCCEEDED(hr));
    
    m_pCurr = pNextBlock;    
    return S_OK;
}
#endif


HRESULT AudioStream::PopulateSample(IMediaSample* pSample)
{
    if (pSample == 0)
        return E_INVALIDARG;

    if (m_pCurr == m_pStop)  //end-of-stream
    {
#ifdef _DEBUG
        odbgstream os;

        os << "AudioStream::PopulateSample: EOS detected";
        
        if (m_pCurr)
            os << "; time=" << m_pCurr->GetTime();
            
        os << endl;
#endif

        return S_FALSE;
    }
    
    assert(m_baseTime >= 0);
    assert((m_baseTime % 100) == 0);
    assert(m_pCurr);
    assert(m_pCurr->GetNumber() == m_pTrack->GetNumber());    
    assert(m_pCurr->GetTime() >= m_baseTime);
    assert((m_pStop == 0) || 
           (m_pStop->GetTimeCode() > m_pCurr->GetTimeCode()));
    
    const Block* const pNextBlock = m_pTrack->GetNext(m_pCurr);

    const LONG srcsize = m_pCurr->GetSize();
    assert(srcsize >= 0);
    
    const long tgtsize = pSample->GetSize();
    tgtsize;
    assert(tgtsize >= 0);
    assert(tgtsize >= srcsize);
    
    BYTE* ptr;

    HRESULT hr = pSample->GetPointer(&ptr);  //read srcsize bytes
    assert(SUCCEEDED(hr));
    assert(ptr);
    
    hr = m_pCurr->Read(ptr);
    assert(hr == S_OK);  //all bytes were read
    
    hr = pSample->SetActualDataLength(srcsize);
    
    hr = pSample->SetPreroll(0);
    assert(SUCCEEDED(hr));
    
    hr = pSample->SetMediaType(0);
    assert(SUCCEEDED(hr));
    
    hr = pSample->SetDiscontinuity(m_bDiscontinuity);
    assert(SUCCEEDED(hr));
    m_bDiscontinuity = false;

    //TODO: this is meaningful for audio streams.  MediaTime is the accumulated
    //sum of audio samples.
    hr = pSample->SetMediaTime(0, 0);
    assert(SUCCEEDED(hr));
    
    //const BlockGroup* const pGroup = m_pCurr->m_pGroup;
    //pGroup;
    //assert(pGroup);
    
    //TODO: you could make an argument that every audio frame is a key frame.
    //Here we decide by fiat that discussion of sync point is only meaningful
    //for video streams, so for audio sync point status is always false.
    hr = pSample->SetSyncPoint(FALSE);
    assert(SUCCEEDED(hr));
    
    const __int64 start_ns = m_pCurr->GetTime();
    assert(start_ns >= m_baseTime);
    assert((start_ns % 100) == 0);
    
    __int64 ns = start_ns - m_baseTime;    
    __int64 start_reftime = ns / 100;
    
    __int64 stop_reftime;
    __int64* pstop_reftime;
            
    if (pNextBlock == 0)
        pstop_reftime = 0;  //TODO: use duration of curr block
    else
    {
        const __int64 stop_ns = pNextBlock->GetTime();
        assert(stop_ns > start_ns);
        assert((stop_ns % 100) == 0);

        ns = stop_ns - m_baseTime;
        stop_reftime = ns / 100;
        assert(stop_reftime > start_reftime);

        pstop_reftime = &stop_reftime;
    }
    
    hr = pSample->SetTime(&start_reftime, pstop_reftime);
    assert(SUCCEEDED(hr));
    
    m_pCurr = pNextBlock;    
    return S_OK;
}



}  //end namespace MkvParser
